package com.bitk.config;


import com.bitk.model.User;
import com.bitk.service.UserService;
import com.bitk.utils.RespBean;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.*;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 配置Spring Security相关
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    UserService userService;

    @Autowired
    private CustomFilterInvocationSecurityMetadataSource customFilterInvocationSecurityMetadataSource;
    @Autowired
    private CustomDecisionManager customDecisionManager;

    @Bean
    PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();//不对密码加密
//        return new BCryptPasswordEncoder();
    }


    /**
     * 进行用户账号密码的验证:配置用户信息,账号密码.角色权限
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService);
    }

    /**
     * 认证设置(HttpSecurity认证用户请求URL认证)
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
//                .anyRequest().authenticated()//任何的请求都需要认证
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        object.setAccessDecisionManager(customDecisionManager);
                        object.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource);
                        return object;
                    }
                })

                .and().formLogin()//开启登录认证
                .loginProcessingUrl("/doLogin")//设置表单action="提交接口"
                /**
                 * 登录成功处理器:
                 *  代码逻辑：登录成功后，返回用户个人信息和成功状态码(JSON类型)。
                 */
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                        response.setContentType("application/json;charset=UTF-8");
                        PrintWriter out = response.getWriter();

                        User user = (User) authentication.getPrincipal();
                        RespBean ok = RespBean.ok("登录成功", user);
                        String json = new ObjectMapper().writeValueAsString(ok);
                        out.write(json);
                        out.flush();// flush()表示强制将缓冲区中的数据发送出去,不必等到缓冲区满
                        out.close();
                        System.err.println("SecurityConfig.class：登录成功");
                    }
                })
                /**
                 * 登录失败处理器：
                 * 代码逻辑：登录失败后，判断失败类型&返回给前端(JSON类型)。
                 */
                .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
                        response.setContentType("application/json;charset=UTF-8");
                        PrintWriter out = response.getWriter();
                        RespBean fail = RespBean.error("登录失败");

                        if (e instanceof LockedException)
                            fail.setMsg("账户被锁定,请联系管理员");
                        else if (e instanceof CredentialsExpiredException)
                            fail.setMsg("密码已过期,请重新登录");
                        else if (e instanceof AccountExpiredException)
                            fail.setMsg("密码过期");
                        else if (e instanceof DisabledException)
                            fail.setMsg("账户被禁用");
                        else if (e instanceof BadCredentialsException)
                            fail.setMsg("用户名或者密码输入错误");
                        String s = new ObjectMapper().writeValueAsString(fail);
                        out.write(s);
                        out.flush();
                        out.close();
                    }
                })
//                .loginPage("/login")这个加不加都一样,登录请求默认为/login
                .permitAll()//表单登录接口公开
                //开启注册接口,接口默认为"/logout"
                .and().logout().logoutSuccessHandler(new LogoutSuccessHandler() {
                    /**
                     * 退出登录成功处理器
                     */
                    @Override
                    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                        response.setContentType("application/json;charset=UTF-8");
                        PrintWriter out = response.getWriter();
                        out.write(new ObjectMapper().writeValueAsString(RespBean.ok("注销成功")));
                        out.flush();
                        out.close();
                    }
                }).permitAll()//开启注销接口&注销接口公开

                .and().csrf().disable();//
    }
}
